<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8" />
   
  <meta name="keywords" content="Java Python" />
   
  <meta name="description" content="From Zero to Hero" />
  
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
  <title>
    JUC-19-AQS |  朱酱酱的学习博客
  </title>
  <meta name="generator" content="hexo-theme-yilia-plus">
  
  <link rel="shortcut icon" href="/favicon.ico" />
  
  
<link rel="stylesheet" href="/css/style.css">

  
<script src="/js/pace.min.js"></script>


  

  

<link rel="alternate" href="/atom.xml" title="朱酱酱的学习博客" type="application/atom+xml">
</head>

</html>

<body>
  <div id="app">
    <main class="content">
      <section class="outer">
  <article id="post-JUC/JUC-19-AQS" class="article article-type-post" itemscope
  itemprop="blogPost" data-scroll-reveal>

  <div class="article-inner">
    
    <header class="article-header">
       
<h1 class="article-title sea-center" style="border-left:0" itemprop="name">
  JUC-19-AQS
</h1>
  

    </header>
    

    
    <div class="article-meta">
      <a href="/2020/08/03/JUC/JUC-19-AQS/" class="article-date">
  <time datetime="2020-08-03T08:02:24.000Z" itemprop="datePublished">2020-08-03</time>
</a>
      
      
      
<div class="word_count">
    <span class="post-time">
        <span class="post-meta-item-icon">
            <i class="ri-quill-pen-line"></i>
            <span class="post-meta-item-text"> 字数统计:</span>
            <span class="post-count">4.7k字</span>
        </span>
    </span>

    <span class="post-time">
        &nbsp; | &nbsp;
        <span class="post-meta-item-icon">
            <i class="ri-book-open-line"></i>
            <span class="post-meta-item-text"> 阅读时长≈</span>
            <span class="post-count">18分钟</span>
        </span>
    </span>
</div>

      
    </div>
    

    
    
    <div class="tocbot"></div>





    

    <div class="article-entry" itemprop="articleBody">
      


      

      
      <h1 id="JUC-19-AQS"><a href="#JUC-19-AQS" class="headerlink" title="JUC-19-AQS"></a>JUC-19-AQS</h1><h2 id="1-AQS结构"><a href="#1-AQS结构" class="headerlink" title="1. AQS结构"></a>1. AQS结构</h2><ul>
<li>AQS（<code>AbstractQueuedSynchronizer</code>）是Java众多锁以及并发工具的基础类，底层采用乐观锁，大量采用CAS操作保证其原子性，并且在并发冲突时，采用自旋方法重试。实现了轻量高效的获取锁。</li>
</ul>
<h3 id="1-1-信号量"><a href="#1-1-信号量" class="headerlink" title="1.1 信号量"></a>1.1 信号量</h3><ul>
<li>在AQS中，状态是由<code>volatile state</code>来表示。</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">int</span> state;</span><br></pre></td></tr></table></figure>



<ul>
<li>该属性值表示锁的状态。<ul>
<li>state为0表示锁未被占用，</li>
<li>state为1表示锁被线程持有，</li>
<li>而state大于1表示锁被重入。</li>
</ul>
</li>
</ul>
<p>而本文分析的是独占锁，那么同一时刻，锁只能被一个线程持有。</p>
<p>不仅需要记录锁的状态，还需要记录当前获取锁的线程，实现重入。可以通过来记录。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">transient</span> Thread exclusiveOwnerThread;</span><br></pre></td></tr></table></figure>



<a id="more"></a>

<h3 id="1-2-等待队列"><a href="#1-2-等待队列" class="headerlink" title="1.2 等待队列"></a>1.2 等待队列</h3><ul>
<li>等待队列采用悲观锁的思想，表示当前所等待的资源，状态或条件短时间内可能无法满足，而调用<code>park</code>方法（借助操作系统）来完成线程的阻塞。</li>
<li>在AQS中，队列时一个双端链表，将当前线程包装成某种类型的数据结构扔到等待队列中。</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">Node</span> </span>&#123;  </span><br><span class="line"><span class="comment">// 节点所代表的线程  </span></span><br><span class="line"><span class="keyword">volatile</span> Thread thread;    </span><br><span class="line"><span class="comment">// 双向链表，每个节点需要保存自己的前驱节点和后继节点的引用  </span></span><br><span class="line"><span class="keyword">volatile</span> Node prev;  </span><br><span class="line"><span class="keyword">volatile</span> Node next;  </span><br><span class="line"><span class="comment">// 线程所处的等待锁的状态，初始化时，该值为0。  </span></span><br><span class="line"><span class="keyword">volatile</span> <span class="keyword">int</span> waitStatus;  </span><br><span class="line"><span class="comment">//队列中节点线程被取消</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> CANCELLED =  <span class="number">1</span>;</span><br><span class="line"><span class="comment">//节点将其前驱节点设置为-1，当前驱节点释放锁后，会自动唤醒该节点。  </span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> SIGNAL    = -<span class="number">1</span>;  </span><br><span class="line"><span class="comment">//线程被重新包装为Node节点，并存入Condition队列中。</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> CONDITION = -<span class="number">2</span>;  </span><br><span class="line"><span class="comment">//共享锁唤醒风暴时，将0-&gt;PROPAGATE，表示被传播唤醒</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> PROPAGATE = -<span class="number">3</span>;  </span><br><span class="line"><span class="comment">// 该属性用于条件队列或者共享锁 。在Condition队列中，使用其作为指针。</span></span><br><span class="line">Node nextWaiter;  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>一般在独占锁下，我们需要关注的就是下面几个参数：</p>
<ul>
<li><code>thread</code>：当前Node所代表的线程；</li>
<li><code>waitStatus</code>：表示节点所处的等待状态；</li>
<li><code>prev next</code>节点的前驱和后继</li>
</ul>
<h3 id="1-3-CAS操作"><a href="#1-3-CAS操作" class="headerlink" title="1.3 CAS操作"></a>1.3 CAS操作</h3><ul>
<li>CAS采用乐观锁机制，保证操作的原子性。一般是改变状态或改变指针（引用）指向。</li>
</ul>
<p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20200817-091408589.png" alt="mark"></p>
<h3 id="1-4-总结"><a href="#1-4-总结" class="headerlink" title="1.4 总结"></a>1.4 总结</h3><p>在AQS源码中：</p>
<ol>
<li>锁属性</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"> <span class="comment">//锁的状态</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">int</span> state;</span><br><span class="line"><span class="comment">//当前持有锁的线程</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">transient</span> Thread exclusiveOwnerThread;</span><br></pre></td></tr></table></figure>

<ol start="2">
<li><code>sync queue</code>相关的属性</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//thread属性为null</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">transient</span> <span class="keyword">volatile</span> Node head; </span><br><span class="line"><span class="keyword">private</span> <span class="keyword">transient</span> <span class="keyword">volatile</span> Node tail; <span class="comment">// 队尾，新入队的节点</span></span><br></pre></td></tr></table></figure>

<ol start="3">
<li><code>Node</code>相关属性</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 节点所代表的线程</span></span><br><span class="line"><span class="keyword">volatile</span> Thread thread;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 双向链表，每个节点需要保存自己的前驱节点和后继节点的引用</span></span><br><span class="line"><span class="keyword">volatile</span> Node prev;</span><br><span class="line"><span class="keyword">volatile</span> Node next;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 线程所处的等待锁的状态，初始化时，该值为0</span></span><br><span class="line"><span class="keyword">volatile</span> <span class="keyword">int</span> waitStatus;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> CANCELLED =  <span class="number">1</span>;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> SIGNAL    = -<span class="number">1</span>;</span><br></pre></td></tr></table></figure>



<h2 id="2-lock-接口"><a href="#2-lock-接口" class="headerlink" title="2. lock 接口"></a>2. lock 接口</h2><p>Lock是一个接口，方法定义如下</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">void lock() &#x2F;&#x2F; 如果锁可用就获得锁，如果锁不可用就阻塞直到锁释放</span><br><span class="line">void lockInterruptibly() &#x2F;&#x2F; 和 lock()方法相似, 但阻塞的线程可中断，抛出 java.lang.InterruptedException异常</span><br><span class="line">boolean tryLock() &#x2F;&#x2F; 非阻塞获取锁;尝试获取锁，如果成功返回true</span><br><span class="line">boolean tryLock(long timeout, TimeUnit timeUnit) &#x2F;&#x2F;带有超时时间的获取锁方法</span><br><span class="line">void unlock() &#x2F;&#x2F; 释放锁</span><br></pre></td></tr></table></figure>

<p>实现Lock接口的类有很多，以下为几个常见的锁实现</p>
<ul>
<li><strong>ReentrantLock：表示重入锁</strong>，它是唯一一个实现了Lock接口的类。重入锁指的是线程在获得锁之后，再次获取该锁不需要阻塞，而是直接关联一次计数器增加重入次数</li>
<li><strong>ReentrantReadWriteLock：重入读写锁，它实现了ReadWriteLock接口</strong>，在这个类中维护了两个锁，一个是ReadLock，一个是WriteLock，他们都分别实现了Lock接口。读写锁是一种适合读多写少的场景下解决线程安全问题的工具，基本原则是：<code>读和读不互斥、读和写互斥、写和写互斥</code>。也就是说涉及到影响数据变化的操作都会存在互斥。</li>
<li><strong>StampedLock： stampedLock是JDK8引入的新的锁机制，可以简单认为是读写锁的一个改进版本</strong>，读写锁虽然通过分离读和写的功能使得读和读之间可以完全并发，但是读和写是有冲突的，如果大量的读线程存在，可能会引起写线程的饥饿。stampedLock是一种乐观的读策略，使得乐观锁完全不会阻塞写线程</li>
</ul>
<h2 id="3-ReentrantLock实现"><a href="#3-ReentrantLock实现" class="headerlink" title="3. ReentrantLock实现"></a>3. ReentrantLock实现</h2><h3 id="3-1-加锁逻辑"><a href="#3-1-加锁逻辑" class="headerlink" title="3.1 加锁逻辑"></a>3.1 加锁逻辑</h3><p><code>ReentrantLock</code>有公平锁和非公平锁两种实现，<strong>默认实现非公平锁。但是可配置为公平锁</strong>：</p>
<figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ReentrantLock <span class="keyword">lock</span>=<span class="keyword">new</span> ReentrantLock(<span class="literal">true</span>);</span><br></pre></td></tr></table></figure>

<p>调用公平锁加锁逻辑：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">void</span> <span class="title">lock</span><span class="params">()</span> </span>&#123;  </span><br><span class="line">    <span class="comment">//开始加锁，将state修改为1</span></span><br><span class="line">    acquire(<span class="number">1</span>); </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>真正的加锁方法：</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">acquire</span><span class="params">(<span class="keyword">int</span> arg)</span> </span>&#123;  </span><br><span class="line">    <span class="keyword">if</span> (!tryAcquire(arg) &amp;&amp;    </span><br><span class="line">        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))   </span><br><span class="line">        selfInterrupt();    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><strong>1. tryAcquire(arg)</strong><br> 该方法由继承AQS的子类实现，为获取锁的具体逻辑；</p>
<p><strong>2. addWaiter(Node.EXCLUSIVE)</strong><br> 该方法由AQS实现，负责在获取锁失败后调用，将当前请求锁的线程包装成Node并且放到<code>等待队列中</code>，并返回该Node。</p>
<p><strong>3. acquireQueued(addWaiter(Node.EXCLUSIVE), arg)</strong><br> 该方法由AQS实现。针对上面加入到队列的Node不断尝试两种操作之一：</p>
<ul>
<li>若前驱节点是head节点的时候，尝试获取锁；</li>
<li>调用<code>park</code>将当前线程挂起，线程阻塞。</li>
</ul>
<p><strong>4. selfInterrupt</strong><br>该方法由AQS实现。<strong>恢复用户行为。</strong></p>
<ul>
<li><p>用户在外界调用<code>t1.interrupt()</code>进行中断。</p>
</li>
<li><p>线程在<code>parkAndCheckInterrupt</code>方法被唤醒之后。会调用<code>Thread.interrupted();</code>判断线程的中断标识，而该方法调用完毕会清除中断标识位。</p>
</li>
<li><p>而AQS为了不改变用户标识。再次调用<code>selfInterrupt</code>恢复用户行为。</p>
</li>
</ul>
<h3 id="3-2-构建等待队列-addWaiter"><a href="#3-2-构建等待队列-addWaiter" class="headerlink" title="3.2 构建等待队列 addWaiter"></a>3.2 构建等待队列 addWaiter</h3><ul>
<li>我们使用<code>ReentrantLock</code>独占锁时，等待队列是<code>延迟加载</code>的。</li>
<li>也就是说<strong>若是线程交替执行</strong>，那么借助<code>信号量（状态）</code>来保证。</li>
<li>若是<strong>线程并发执行</strong>，就需要将阻塞线程放入到队列中。</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//注意这个方法可能存在并发问题。</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> Node <span class="title">addWaiter</span><span class="params">(Node mode)</span> </span>&#123;  </span><br><span class="line">    Node node = <span class="keyword">new</span> Node(Thread.currentThread(), mode);  </span><br><span class="line">    Node pred = tail;  </span><br><span class="line">    <span class="comment">//队列已经存在</span></span><br><span class="line">    <span class="keyword">if</span> (pred != <span class="keyword">null</span>) &#123;  </span><br><span class="line">        node.prev = pred;  </span><br><span class="line">        <span class="keyword">if</span> (compareAndSetTail(pred, node)) &#123;  </span><br><span class="line">            pred.next = node;  </span><br><span class="line">            <span class="keyword">return</span> node;  </span><br><span class="line">        &#125;  </span><br><span class="line">    &#125;  </span><br><span class="line">    <span class="comment">//队列不存在</span></span><br><span class="line">    enq(node);  </span><br><span class="line">    <span class="keyword">return</span> node;  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<ol>
<li>队列不存在的情况(初始化队列 enq(Node))</li>
</ol>
<p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20200817-094601871.png" alt="mark"></p>
<p>注意，该方法处理<code>CAS</code>操作是原子性的，其他操作都存在并发冲突问题。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> Node <span class="title">enq</span><span class="params">(<span class="keyword">final</span> Node node)</span> </span>&#123;  </span><br><span class="line">     <span class="keyword">for</span> (;;) &#123;  </span><br><span class="line">         Node t = tail;  </span><br><span class="line">         <span class="comment">// 初始化队列 （一个Thread 为null的空node）</span></span><br><span class="line">         <span class="keyword">if</span> (t == <span class="keyword">null</span>) &#123; <span class="comment">// Must initialize  </span></span><br><span class="line">             <span class="keyword">if</span> (compareAndSetHead(<span class="keyword">new</span> Node()))  </span><br><span class="line">                 tail = head;  </span><br><span class="line">         &#125; <span class="keyword">else</span> &#123;  </span><br><span class="line">             <span class="comment">// (维护队列 CAS改变队尾)</span></span><br><span class="line">             node.prev = t;  </span><br><span class="line">             <span class="keyword">if</span> (compareAndSetTail(t, node)) &#123;  </span><br><span class="line">                 t.next = node;  </span><br><span class="line">                 <span class="keyword">return</span> t;  </span><br><span class="line">             &#125;  </span><br><span class="line">         &#125;  </span><br><span class="line">     &#125;  </span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>



<p>该方法采用自旋+CAS。CAS是保证同一时刻只有一个线程能成功改变引用的指向。</p>
<p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20200817-094805424.png" alt="mark"></p>
<ul>
<li><p>根据上面的流程图，sync queue的创建过程。<strong>head节点是<code>new Node()</code>产生的，即其中的属性为默认值。</strong>也就是thread属性为null。也就是说<strong>正在执行的线程也会在<code>sync queue</code>中占据头节点，但是节点中不会保存线程信息。</strong></p>
<p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20200817-094839510.png" alt="mark"></p>
</li>
</ul>
<p><strong>尾分叉问题：</strong></p>
<p>上面已经说了，该方法是线程不安全的。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//步骤1：可能多个节点的prev指针都指向尾结点，导致尾分叉</span></span><br><span class="line">node.prev = t;  </span><br><span class="line"><span class="comment">//步骤2：但同一时刻，tail引用只会执行一个node。</span></span><br><span class="line"><span class="keyword">if</span> (compareAndSetTail(t, node)) &#123;  </span><br><span class="line">   <span class="comment">//步骤3：现在环境是线程安全，旧尾结点的后继指针指向新尾结点。</span></span><br><span class="line">   t.next = node;  </span><br><span class="line">   <span class="keyword">return</span> t;  </span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>

<p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20200817-094920922.png" alt="mark"></p>
<p>执行完<code>步骤2</code>，但<code>步骤3</code>还未执行时，恰好有线程从头节点开始往后遍历。<strong>此时（旧）尾结点中的next域还为null。</strong>它是遍历不到新加进来的尾结点的。这显然是不合理的。</p>
<p>但此时<code>步骤1</code>是执行成功的，所以<strong>若是tail节点往前遍历，实际上是可以遍历到所有节点的</strong>，这也是为什么在AQS源码中，有时候常常会出现<strong>从尾结点开始逆向遍历链表的情况</strong>。</p>
<p>那些“分叉”的节点，肯定会入队失败。那么继续自旋，等待所有的线程节点全部入队成功。</p>
<h3 id="3-3-尝试获取锁-tryAcquire"><a href="#3-3-尝试获取锁-tryAcquire" class="headerlink" title="3.3 尝试获取锁 tryAcquire"></a>3.3 尝试获取锁 tryAcquire</h3><ul>
<li><strong>根据标志位state，来判断锁是否被占用</strong>。此时可能锁未被占用，由于是公平锁，于是会去判断<code>sync queue</code>中是否有人在排队。</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">tryAcquire</span><span class="params">(<span class="keyword">int</span> acquires)</span> </span>&#123;  </span><br><span class="line">    <span class="comment">//获取当前线程</span></span><br><span class="line">    <span class="keyword">final</span> Thread current = Thread.currentThread();  </span><br><span class="line">    <span class="comment">//获取Lock对象的上锁情况，0-表示无线程持有；1-表示被线程持有；大于1-表示锁被重入</span></span><br><span class="line">    <span class="keyword">int</span> c = getState();  </span><br><span class="line">    <span class="comment">//若此刻无人占有锁</span></span><br><span class="line">    <span class="keyword">if</span> (c == <span class="number">0</span>) &#123;  </span><br><span class="line">        <span class="keyword">if</span> (!hasQueuedPredecessors() &amp;&amp;    <span class="comment">//判断队列中是否有前辈。若返回false代表没有，开始尝试加锁</span></span><br><span class="line">            compareAndSetState(<span class="number">0</span>, acquires)) &#123;   <span class="comment">//此刻队列中没有存在前辈，尝试加锁</span></span><br><span class="line">            setExclusiveOwnerThread(current);   <span class="comment">//将当前线程修改为持有锁的线程（后续判断可重入）</span></span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">true</span>;  </span><br><span class="line">        &#125;  </span><br><span class="line">    &#125;  </span><br><span class="line">    <span class="comment">//若是当前线程是持有锁的线程</span></span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (current == getExclusiveOwnerThread()) &#123;  </span><br><span class="line">        <span class="comment">//当前状态+1</span></span><br><span class="line">        <span class="keyword">int</span> nextc = c + acquires;  </span><br><span class="line">        <span class="keyword">if</span> (nextc &lt; <span class="number">0</span>)  </span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> Error(<span class="string">"Maximum lock count exceeded"</span>);  </span><br><span class="line">        setState(nextc);  </span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;  </span><br><span class="line">    &#125;  </span><br><span class="line">    <span class="comment">//否则，代表加锁失败</span></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">false</span>;  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//该方法不具有原子性，可能多个线程都觉得自己不需要排队，最终还是依靠外面</span></span><br><span class="line"><span class="comment">//条件上的CAS来保持其原子性。</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">hasQueuedPredecessors</span><span class="params">()</span> </span>&#123;  </span><br><span class="line">    Node t = tail;   </span><br><span class="line">    Node h = head;  </span><br><span class="line">    Node s;  </span><br><span class="line">    <span class="keyword">return</span> h != t &amp;&amp;  </span><br><span class="line">        ((s = h.next) == <span class="keyword">null</span> || s.thread != Thread.currentThread());  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>上述方法是<code>判断队列中是否存在元素</code>。可能存在以下几种情况：</p>
<ul>
<li><strong>此时未维护队列</strong>，<code>h!=t</code>返回false，即无人排队；</li>
<li>此时队列<strong>只有头节点</strong>（哑结点），<code>h!=t</code>返回false，即无人排队；</li>
<li>此时队列中<strong>存在2个的节点</strong>。若线程是头结点的后继节点线程（即处理正在办理业务的线程，进来的线程是第一个排队的线程）。那么<code>s.thread != Thread.currentThread()</code>返回false，即可是尝试加锁。</li>
<li>队列存在<strong>2个以上节点</strong>，且进来的线程不是第一个排队的线程，那么该线程需要乖乖的排队</li>
</ul>
<p>当然该方法不是并发安全的方法，即可能存在多个线程觉得自己无需排队，最终还是依靠<code>CAS</code>来争夺锁。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (!hasQueuedPredecessors() &amp;&amp;  compareAndSetState(<span class="number">0</span>, acquires)) &#123;</span><br><span class="line">    <span class="comment">//线程安全   </span></span><br><span class="line">    setExclusiveOwnerThread(current);  </span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">true</span>;  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>同一时刻，只有一个线程可以成功改变state的状态。记录该线程为独占锁线程，一般后续可以重入。</p>
<p>没成功获取锁那么会调用<code>2.2 中的方法，将该线程加入到阻塞队列中</code>。</p>
<h3 id="3-4-阻塞线程-acquireQueued"><a href="#3-4-阻塞线程-acquireQueued" class="headerlink" title="3.4 阻塞线程 acquireQueued"></a>3.4 阻塞线程 acquireQueued</h3><ul>
<li>若执行到该方法，说明<code>addWaiter</code>方法已经成功将该线程包装为Node节点放到了队尾。</li>
<li>在该方法中依旧尝试获取锁；</li>
<li>再次获取锁失败后，会将其阻塞；</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">acquireQueued</span><span class="params">(<span class="keyword">final</span> Node node, <span class="keyword">int</span> arg)</span> </span>&#123;  </span><br><span class="line">    <span class="keyword">boolean</span> failed = <span class="keyword">true</span>;  </span><br><span class="line">    <span class="keyword">try</span> &#123;  </span><br><span class="line">        <span class="keyword">boolean</span> interrupted = <span class="keyword">false</span>;  </span><br><span class="line">        <span class="keyword">for</span> (;;) &#123;  </span><br><span class="line">            <span class="comment">//获取node的前驱节点</span></span><br><span class="line">            <span class="keyword">final</span> Node p = node.predecessor();  </span><br><span class="line">            <span class="comment">//若前驱节点在办理业务，那么它将再次获取一次锁。</span></span><br><span class="line">            <span class="keyword">if</span> (p == head &amp;&amp; tryAcquire(arg)) &#123;  </span><br><span class="line">                <span class="comment">//获取锁成功，此处便是线程安全。</span></span><br><span class="line">                <span class="comment">//将自己设置为头节点，并将自己设置为哑节点</span></span><br><span class="line">                setHead(node);  </span><br><span class="line">                p.next = <span class="keyword">null</span>; <span class="comment">// help GC  </span></span><br><span class="line">                failed = <span class="keyword">false</span>;  </span><br><span class="line">                <span class="keyword">return</span> interrupted;  </span><br><span class="line">            &#125;  </span><br><span class="line">            <span class="comment">//获取锁失败，将自己挂起。</span></span><br><span class="line">            <span class="keyword">if</span> (shouldParkAfterFailedAcquire(p, node) &amp;&amp;  </span><br><span class="line">                parkAndCheckInterrupt())  </span><br><span class="line">                interrupted = <span class="keyword">true</span>;  </span><br><span class="line">        &#125;  </span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;  </span><br><span class="line">        <span class="keyword">if</span> (failed)  </span><br><span class="line">            cancelAcquire(node);  </span><br><span class="line">    &#125;  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上述方法时<code>自旋方法</code>，而出口就是获取到锁。若线程获取不到锁，便会将自己阻塞。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//该方法时node线程获取锁成功后执行的，故是线程安全的。</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">setHead</span><span class="params">(Node node)</span> </span>&#123;  </span><br><span class="line">    head = node;  </span><br><span class="line">    node.thread = <span class="keyword">null</span>;  </span><br><span class="line">    node.prev = <span class="keyword">null</span>;  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20200817-103752407.png" alt="mark"></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">shouldParkAfterFailedAcquire</span><span class="params">(Node pred, Node node)</span> </span>&#123;  </span><br><span class="line">    <span class="comment">//上一个节点的waitStatus</span></span><br><span class="line">    <span class="keyword">int</span> ws = pred.waitStatus;  </span><br><span class="line">    <span class="comment">//  Node.SIGNAL==-1</span></span><br><span class="line">    <span class="keyword">if</span> (ws == Node.SIGNAL)  </span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;  </span><br><span class="line">    <span class="comment">//ws大于0，则说明该节点已经被取消了。</span></span><br><span class="line">    <span class="keyword">if</span> (ws &gt; <span class="number">0</span>) &#123;  </span><br><span class="line">        <span class="keyword">do</span> &#123;  </span><br><span class="line">            node.prev = pred = pred.prev;  </span><br><span class="line">        &#125; <span class="keyword">while</span> (pred.waitStatus &gt; <span class="number">0</span>);  </span><br><span class="line">        pred.next = node;  </span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;  </span><br><span class="line">        <span class="comment">//CAS变更ws的状态</span></span><br><span class="line">        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);  </span><br><span class="line">    &#125;  </span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">false</span>;  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>上述方法是加锁失败开始执行的。也就是一个线程决定挂起之前需要执行的操作。这里就用到了节点中的信号量<code>waitStatus</code>。</p>
<ol>
<li>判断前驱节点<code>waitStatus</code>的值，会做出如下操作：<br> 1.1 前驱节点<code>waitStatus</code>若是-1，直接返回true。<br> 1.2 前驱节点<code>waitStatus</code>若大于0，证明前驱节点已被取消，那么在链表中删除前驱节点，直到node的前驱节点的<code>waitStatus</code>不大于0为止。然后返回false<br> 1.3. 若前驱节点<code>waitStatus</code>等于0，使用CAS尝试改变前驱节点<code>waitStatus</code>状态，由0到-1，然后返回false。</li>
</ol>
<ol start="2">
<li>若是返回true，那么去阻塞该节点，若是返回false，那么继续自旋，继续上述过程，直至该方法返回true为止，方法返回true，便会执行下列方法，阻塞线程。</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">parkAndCheckInterrupt</span><span class="params">()</span> </span>&#123;  </span><br><span class="line">    <span class="comment">//将线程挂起</span></span><br><span class="line">    LockSupport.park(<span class="keyword">this</span>);  </span><br><span class="line">    <span class="comment">//线程被唤起时，查看线程的中断标识（注意，查看完毕后，中断标识归位）</span></span><br><span class="line">    <span class="keyword">return</span> Thread.interrupted();  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>需要注意的是：当前节点在阻塞之前，会将前驱节点的<code>waitStatus</code>设置为-1，就可保证前驱节点在适当的时机唤醒自己。</strong></p>
<h2 id="4-附录"><a href="#4-附录" class="headerlink" title="4. 附录"></a>4. 附录</h2><h3 id="4-1-CAS对象"><a href="#4-1-CAS对象" class="headerlink" title="4.1 CAS对象"></a>4.1 CAS对象</h3><p>开始我认为对象的CAS算法，实际上会是B对象去覆盖堆内存上的A对象，其实不然。比较交换的是引用。</p>
<figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//该方法是获取引用。而非堆上的内存。</span></span><br><span class="line"><span class="keyword">static</span> &#123;  </span><br><span class="line">    <span class="keyword">try</span> &#123;  </span><br><span class="line">        valueOffset = unsafe.objectFieldOffset  </span><br><span class="line">            (AtomicReference.class.getDeclaredField(<span class="string">"value"</span>));  </span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception ex) &#123; <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(ex); &#125;  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="5-加锁总结"><a href="#5-加锁总结" class="headerlink" title="5. 加锁总结"></a>5. 加锁总结</h2><ol>
<li>因为AQS的等待队列是延迟加载，只有多个线程并发访问时，才会开始维护队列。</li>
<li>因为head节点中不包含<code>thread</code>属性的值，又被称为<code>哑节点</code>。</li>
<li>head是正在办理业务的节点，而他的后继节点是第一个排队节点。</li>
</ol>
<blockquote>
<ol>
<li>尝试加锁过程</li>
</ol>
</blockquote>
<ol>
<li>根据status判断当前锁是否被持有，若被持有，直接<code>维护队列</code>；</li>
<li>若未被持有，判断当前队列是否有节点在排队，若有节点排队，直接<code>维护队列</code>；</li>
<li>若无节点排队，则通过CAS修改锁状态标识，修改成功代表线程持有该锁；</li>
<li>使用<code>exclusiveOwnerThread</code>来保存持有锁的线程（解决线程重入）；</li>
</ol>
<blockquote>
<ol start="2">
<li>维护队列过程</li>
</ol>
</blockquote>
<p>最终线程的head节点为哑节点。后续线程被组装成node节点，维护在链表中。</p>
<blockquote>
<ol start="3">
<li>线程阻塞过程</li>
</ol>
</blockquote>
<ol>
<li>判断node节点是否为head节点的后续节点（第一个排队节点），若是的话，尝试获取锁。若获取到，将其设置为head节点，并将其设置为哑节点；</li>
<li>在阻塞前，会将自己的前驱节点的<code>waitStatus</code>设置为<code>SIGNAL</code>。以便可以唤醒自己。</li>
</ol>
<h2 id="6-锁的释放"><a href="#6-锁的释放" class="headerlink" title="6. 锁的释放"></a>6. 锁的释放</h2><h3 id="ReentrantLock-unlock"><a href="#ReentrantLock-unlock" class="headerlink" title="ReentrantLock.unlock"></a>ReentrantLock.unlock</h3><p>加锁的过程分析完以后，再来分析一下释放锁的过程，调用release方法，这个方法里面做两件事，</p>
<p><strong>1，释放锁 ；</strong></p>
<p><strong>2，唤醒park的线程</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">release</span><span class="params">(<span class="keyword">int</span> arg)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (tryRelease(arg)) &#123;</span><br><span class="line">        Node h = head;</span><br><span class="line">        <span class="keyword">if</span> (h != <span class="keyword">null</span> &amp;&amp; h.waitStatus != <span class="number">0</span>)</span><br><span class="line">            unparkSuccessor(h);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="tryRelease"><a href="#tryRelease" class="headerlink" title="tryRelease"></a>tryRelease</h3><ul>
<li>这个动作可以认为就是一个设置锁状态的操作，而且是将状态减掉传入的参数值（参数是1），如果结果状态为0，就将排它锁的Owner设置为null，以使得其它的线程有机会进行执行。</li>
<li>在排它锁中，加锁的时候状态会增加1（当然可以自己修改这个值），在解锁的时候减掉1，同一个锁，在可以重入后，可能会被叠加为2、3、4这些值，只有unlock()的次数与lock()的次数对应才会将Owner线程设置为空，而且也只有这种情况下才会返回true。</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">tryRelease</span><span class="params">(<span class="keyword">int</span> releases)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">int</span> c = getState() - releases; <span class="comment">// 这里是将锁的数量减1</span></span><br><span class="line">    <span class="keyword">if</span> (Thread.currentThread() != getExclusiveOwnerThread())<span class="comment">// 如果释放的线程和获取锁的线程不是同一个，抛出非法监视器状态异常</span></span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> IllegalMonitorStateException();</span><br><span class="line">    <span class="keyword">boolean</span> free = <span class="keyword">false</span>;</span><br><span class="line">    <span class="keyword">if</span> (c == <span class="number">0</span>) &#123; </span><br><span class="line"><span class="comment">// 由于重入的关系，不是每次释放锁c都等于0，</span></span><br><span class="line">    <span class="comment">// 直到最后一次释放锁时，才会把当前线程释放</span></span><br><span class="line">        free = <span class="keyword">true</span>;</span><br><span class="line">        setExclusiveOwnerThread(<span class="keyword">null</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    setState(c);</span><br><span class="line">    <span class="keyword">return</span> free;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>





<h3 id="unparkSuccessor"><a href="#unparkSuccessor" class="headerlink" title="unparkSuccessor"></a>unparkSuccessor</h3><ul>
<li>在方法unparkSuccessor(Node)中，就意味着真正要释放锁了，它传入的是head节点（head节点是占用锁的节点），当前线程被释放之后，需要唤醒下一个节点的线程</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">unparkSuccessor</span><span class="params">(Node node)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">int</span> ws = node.waitStatus;</span><br><span class="line">    <span class="keyword">if</span> (ws &lt; <span class="number">0</span>)</span><br><span class="line">        compareAndSetWaitStatus(node, ws, <span class="number">0</span>);</span><br><span class="line">    Node s = node.next;</span><br><span class="line">    <span class="keyword">if</span> (s == <span class="keyword">null</span> || s.waitStatus &gt; <span class="number">0</span>) &#123;<span class="comment">//判断后继节点是否为空或者是否是取消状态,</span></span><br><span class="line">        s = <span class="keyword">null</span>;</span><br><span class="line">        <span class="keyword">for</span> (Node t = tail; t != <span class="keyword">null</span> &amp;&amp; t != node; t = t.prev)</span><br><span class="line">            <span class="keyword">if</span> (t.waitStatus &lt;= <span class="number">0</span>) <span class="comment">//然后从队列尾部向前遍历找到最前面的一个waitStatus小于0的节点, 至于为什么从尾部开始向前遍历，因为在doAcquireInterruptibly.cancelAcquire方法的处理过程中只设置了next的变化，没有设置prev的变化，在最后有这样一行代码：node.next = node，如果这时执行了unparkSuccessor方法，并且向后遍历的话，就成了死循环了，所以这时只有prev是稳定的</span></span><br><span class="line">                s = t;</span><br><span class="line">    &#125;</span><br><span class="line"><span class="comment">//内部首先会发生的动作是获取head节点的next节点，如果获取到的节点不为空，则直接通过：“LockSupport.unpark()”方法来释放对应的被挂起的线程，这样一来将会有一个节点唤醒后继续进入循环进一步尝试tryAcquire()方法来获取锁</span></span><br><span class="line">    <span class="keyword">if</span> (s != <span class="keyword">null</span>)</span><br><span class="line">        LockSupport.unpark(s.thread); <span class="comment">//释放许可</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><strong>移除锁的节点图文解析</strong></p>
<ul>
<li>head节点表示获取锁成功的节点，当头结点在释放同步状态时，会唤醒后继节点，如果后继节点获得锁成功，会把自己设置为头结点，节点的变化过程如下</li>
</ul>
<p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20200918/103308919.png" alt="mark"></p>
<p>这个过程也是涉及到两个变化</p>
<ul>
<li>修改head节点指向下一个获得锁的节点</li>
<li>新的获得锁的节点，将prev的指针指向null</li>
</ul>
<p><strong>注意：</strong></p>
<ul>
<li><strong>这里有一个小的变化，就是设置head节点不需要用CAS，原因是设置head节点是由获得锁的线程来完成的，而同步锁只能由一个线程获得，所以不需要CAS保证</strong></li>
<li><strong>只需要把head节点设置为原首节点的后继节点，并且断开原head节点的next引用即可</strong></li>
</ul>
<p><strong>参考博客</strong></p>
<p><a href="https://www.jianshu.com/p/dcc608274b88" target="_blank" rel="noopener">https://www.jianshu.com/p/dcc608274b88</a></p>
<p><a href="https://segmentfault.com/a/1190000017372067" target="_blank" rel="noopener">https://segmentfault.com/a/1190000017372067</a></p>
<p><a href="https://www.cnblogs.com/fsmly/p/11274572.html" target="_blank" rel="noopener">https://www.cnblogs.com/fsmly/p/11274572.html</a></p>

      
      <!-- reward -->
      
      <div id="reward-btn">
        打赏
      </div>
      
    </div>
      <!-- copyright -->
      
        <div class="declare">
          <ul class="post-copyright">
            <li>
              <i class="ri-copyright-line"></i>
              <strong>版权声明： </strong s>
              本博客所有文章除特别声明外，均采用 <a href="https://www.apache.org/licenses/LICENSE-2.0.html" rel="external nofollow"
                target="_blank">Apache License 2.0</a> 许可协议。转载请注明出处！
            </li>
          </ul>
        </div>
        
    <footer class="article-footer">
      
          
<div class="share-btn">
      <span class="share-sns share-outer">
        <i class="ri-share-forward-line"></i>
        分享
      </span>
      <div class="share-wrap">
        <i class="arrow"></i>
        <div class="share-icons">
          
          <a class="weibo share-sns" href="javascript:;" data-type="weibo">
            <i class="ri-weibo-fill"></i>
          </a>
          <a class="weixin share-sns wxFab" href="javascript:;" data-type="weixin">
            <i class="ri-wechat-fill"></i>
          </a>
          <a class="qq share-sns" href="javascript:;" data-type="qq">
            <i class="ri-qq-fill"></i>
          </a>
          <a class="douban share-sns" href="javascript:;" data-type="douban">
            <i class="ri-douban-line"></i>
          </a>
          <!-- <a class="qzone share-sns" href="javascript:;" data-type="qzone">
            <i class="icon icon-qzone"></i>
          </a> -->
          
          <a class="facebook share-sns" href="javascript:;" data-type="facebook">
            <i class="ri-facebook-circle-fill"></i>
          </a>
          <a class="twitter share-sns" href="javascript:;" data-type="twitter">
            <i class="ri-twitter-fill"></i>
          </a>
          <a class="google share-sns" href="javascript:;" data-type="google">
            <i class="ri-google-fill"></i>
          </a>
        </div>
      </div>
</div>

<div class="wx-share-modal">
    <a class="modal-close" href="javascript:;"><i class="ri-close-circle-line"></i></a>
    <p>扫一扫，分享到微信</p>
    <div class="wx-qrcode">
      <img src="//api.qrserver.com/v1/create-qr-code/?size=150x150&data=http://zhuuu.work/2020/08/03/JUC/JUC-19-AQS/" alt="微信分享二维码">
    </div>
</div>

<div id="share-mask"></div>
      
      
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/AQS/" rel="tag">AQS</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/JUC/" rel="tag">JUC</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/" rel="tag">多线程</a></li></ul>


    </footer>

  </div>

  
  
  <nav class="article-nav">
    
      <a href="/2020/08/03/Mysql/Mysql-10-%E4%B9%90%E8%A7%82%E9%94%81%E5%92%8C%E6%82%B2%E8%A7%82%E9%94%81/" class="article-nav-link">
        <strong class="article-nav-caption">上一篇</strong>
        <div class="article-nav-title">
          
            Mysql-10-乐观锁和悲观锁
          
        </div>
      </a>
    
    
      <a href="/2020/08/02/NetworkCoding/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C-07-%E7%89%A9%E7%90%86%E5%B1%82/" class="article-nav-link">
        <strong class="article-nav-caption">下一篇</strong>
        <div class="article-nav-title">计算机网络-07-物理层</div>
      </a>
    
  </nav>


  

  
  
<!-- valine评论 -->
<div id="vcomments-box">
    <div id="vcomments">
    </div>
</div>
<script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
<script src='https://cdn.jsdelivr.net/npm/valine@1.3.10/dist/Valine.min.js'></script>
<script>
    new Valine({
        el: '#vcomments',
        notify: false,
        verify: '',
        app_id: '',
        app_key: '',
        path: window.location.pathname,
        avatar: 'mp',
        placeholder: '给我的文章加点评论吧~',
        recordIP: true
    });
    const infoEle = document.querySelector('#vcomments .info');
    if (infoEle && infoEle.childNodes && infoEle.childNodes.length > 0) {
        infoEle.childNodes.forEach(function (item) {
            item.parentNode.removeChild(item);
        });
    }
</script>
<style>
    #vcomments-box {
        padding: 5px 30px;
    }

    @media screen and (max-width: 800px) {
        #vcomments-box {
            padding: 5px 0px;
        }
    }

    #vcomments-box #vcomments {
        background-color: #fff;
    }

    .v .vlist .vcard .vh {
        padding-right: 20px;
    }

    .v .vlist .vcard {
        padding-left: 10px;
    }
</style>

  

  
  
<div class="gitalk" id="gitalk-container"></div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/gitalk@1.5.0/dist/gitalk.css">


<script src="https://cdn.jsdelivr.net/npm/gitalk@1.5.0/dist/gitalk.min.js"></script>


<script src="https://cdn.jsdelivr.net/npm/blueimp-md5@2.10.0/js/md5.min.js"></script>

<script type="text/javascript">
  var gitalk = new Gitalk({
    clientID: 'db188ed8c86dc4b0dbf3',
    clientSecret: 'a58f92160e5a9efd726b7d533000a0737f3e3f3e',
    repo: 'Blog-comments',
    owner: 'Zhuuuuuuuu',
    admin: ['Zhuuuuuuuu'],
    // id: location.pathname,      // Ensure uniqueness and length less than 50
    id: md5(location.pathname),
    distractionFreeMode: false,  // Facebook-like distraction free mode
    pagerDirection: 'last'
  })

  gitalk.render('gitalk-container')
</script>

  

</article>
</section>
      <footer class="footer">
  <div class="outer">
    <ul class="list-inline">
      <li>
        &copy;
        2019-2021
        Zhuuu
      </li>
      <li>
        
      </li>
    </ul>
    <ul class="list-inline">
      <li>
        
        
        <span>
  <i>PV:<span id="busuanzi_value_page_pv"></span></i>
  <i>UV:<span id="busuanzi_value_site_uv"></span></i>
</span>
        
      </li>
      <li>
        <!-- cnzz统计 -->
        
        <script type="text/javascript" src='https://s9.cnzz.com/z_stat.php?id=1278069914&amp;web_id=1278069914'></script>
        
      </li>
    </ul>
  </div>
</footer>
    <div class="to_top">
        <div class="totop" id="totop">
  <i class="ri-arrow-up-line"></i>
</div>
      </div>
    </main>
      <aside class="sidebar">
        <button class="navbar-toggle"></button>
<nav class="navbar">
  
  <div class="logo">
    <a href="/"><img src="/images/ayer-side.svg" alt="朱酱酱的学习博客"></a>
  </div>
  
  <ul class="nav nav-main">
    
    <li class="nav-item">
      <a class="nav-item-link" href="/">主页</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags">标签</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/JVM/">JVM</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/JDK%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/">JDK源码</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/">多线程</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/Mysql/">Mysql</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/Redis/">Redis</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/%E8%AE%BE%E8%AE%A1%E8%80%85%E6%A8%A1%E5%BC%8F/">设计模式</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/MyBatis/">MyBatis</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/SpringMVC/">SpringMVC</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/Spring/">Spring</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/SpringBoot/">SpringBoot</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/">Linux</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/Leetcode/">Leetcode</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/%E5%89%8D%E7%AB%AF/">前端</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B/">网络编程</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/photoshop/">photoshop</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="http://smartzhuuu.lofter.com/" target="_blank" rel="noopener">摄影</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/2020/about">关于我</a>
    </li>
    
  </ul>
</nav>
<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
      <a class="nav-item-link nav-item-search"  title="Search">
        <i class="ri-search-line"></i>
      </a>
      
      
      <a class="nav-item-link" target="_blank" href="/atom.xml" title="RSS Feed">
        <i class="ri-rss-line"></i>
      </a>
      
    </li>
  </ul>
</nav>
<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
      </aside>
      <div id="mask"></div>

<!-- #reward -->
<div id="reward">
  <span class="close"><i class="ri-close-line"></i></span>
  <p class="reward-p"><i class="ri-cup-line"></i>请我喝杯咖啡吧~</p>
  <div class="reward-box">
    
    <div class="reward-item">
      <img class="reward-img" src="/images/alipay.jpg">
      <span class="reward-type">支付宝</span>
    </div>
    
    
    <div class="reward-item">
      <img class="reward-img" src="/images/wechat.jpg">
      <span class="reward-type">微信</span>
    </div>
    
  </div>
</div>
      
<script src="/js/jquery-2.0.3.min.js"></script>


<script src="/js/jquery.justifiedGallery.min.js"></script>


<script src="/js/lazyload.min.js"></script>


<script src="/js/busuanzi-2.3.pure.min.js"></script>


<script src="/js/share.js"></script>



<script src="/fancybox/jquery.fancybox.min.js"></script>




<script>
  try {
    var typed = new Typed("#subtitle", {
    strings: ['昨夜西风凋碧树。独上高楼，望尽天涯路','衣带渐宽终不悔，为伊消得人憔悴。','众里寻他千百度。蓦然回首，那人却在，灯火阑珊处。'],
    startDelay: 0,
    typeSpeed: 200,
    loop: true,
    backSpeed: 100,
    showCursor: true
    });
  } catch (err) {
  }
  
</script>




<script src="/js/tocbot.min.js"></script>

<script>
  // Tocbot_v4.7.0  http://tscanlin.github.io/tocbot/
  tocbot.init({
    tocSelector: '.tocbot',
    contentSelector: '.article-entry',
    headingSelector: 'h1, h2, h3, h4, h5, h6',
    hasInnerContainers: true,
    scrollSmooth: true,
    scrollContainer:'main',
    positionFixedSelector: '.tocbot',
    positionFixedClass: 'is-position-fixed',
    fixedSidebarOffset: 'auto',
    onClick: (e) => {
      $('.toc-link').removeClass('is-active-link');
      $(`a[href=${e.target.hash}]`).addClass('is-active-link');
      $(e.target.hash).scrollIntoView();
      return false;
    }
  });
</script>


<script>
  var ayerConfig = {
    mathjax: true
  }
</script>


<script src="/js/ayer.js"></script>


<script src="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.css">


<!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.css">
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js"></script>

<script>
    function viewer_init() {
        let pswpElement = document.querySelectorAll('.pswp')[0];
        let $imgArr = document.querySelectorAll(('.article-entry img:not(.reward-img)'))

        $imgArr.forEach(($em, i) => {
            $em.onclick = () => {
                // slider展开状态
                // todo: 这样不好，后面改成状态
                if (document.querySelector('.left-col.show')) return
                let items = []
                $imgArr.forEach(($em2, i2) => {
                    let img = $em2.getAttribute('data-idx', i2)
                    let src = $em2.getAttribute('data-target') || $em2.getAttribute('src')
                    let title = $em2.getAttribute('alt')
                    // 获得原图尺寸
                    const image = new Image()
                    image.src = src
                    items.push({
                        src: src,
                        w: image.width || $em2.width,
                        h: image.height || $em2.height,
                        title: title
                    })
                })
                var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
                    index: parseInt(i)
                });
                gallery.init()
            }
        })
    }
    viewer_init()
</script>



<script type="text/x-mathjax-config">
  MathJax.Hub.Config({
      tex2jax: {
          inlineMath: [ ['$','$'], ["\\(","\\)"]  ],
          processEscapes: true,
          skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
      }
  });

  MathJax.Hub.Queue(function() {
      var all = MathJax.Hub.getAllJax(), i;
      for(i=0; i < all.length; i += 1) {
          all[i].SourceElement().parentNode.className += ' has-jax';
      }
  });
</script>

<script src="https://cdn.jsdelivr.net/npm/mathjax@2.7.6/unpacked/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>


<script type="text/javascript" src="https://js.users.51.la/20544303.js"></script>
  </div>
</body>

</html>